/*
  JSON lexical analyzer
*/

#define JSON_PRIVATE_
#include "json.h"


// Lexer automata states enumeration
typedef enum {
  ST_TERMINAL,
  ST_STRING,
  ST_INTEGER,
  ST_REAL,
  ST_UNICODE
} lexer_state_t;

static int  tail = 0;
static char stack[JSON_LEXER_STACK_MAX] = "";
static lexer_state_t state = ST_TERMINAL;               // Lexer automata state

static inline void push(char c);
static inline char pop();
static inline char peek();

static void push(char c)
{
  if (tail < JSON_LEXER_STACK_MAX)
    stack[tail++] = c;
  else {
    json_error("lexer stack overrun");
    json_lexer_reset();
  }
}

static char pop()
{
  if (tail > 0 && tail < JSON_LEXER_STACK_MAX)
    return stack[--tail];
  else {
    json_error("lexer stack underrun");
    json_lexer_reset();
    return 0;
  }
}

static char peek()
{
  return stack[tail - 1];
}

static void reset()
{
  tail = 0;
}

static bool escape()
{
  return (tail > 0 && peek() == '\\');
}

void json_lexer_reset()
{
  state = ST_TERMINAL;
  tail = 0;
}

bool json_get_symbol(char c, Symbol* out)
{
  bool accepted = true; //If input char accepted (according to LL1)
    
  if (state == ST_TERMINAL){               // Recognize terminals
    if (c == ' ' || c == '\r' || c == '\n'){
      //pass
    }
    else if ((c >= '0' && c <='9') || c == '-') { // Digit?
      state = ST_INTEGER; reset(); push(c);         // Save current char to stack
    }
    else if (c == '{') out->sym = lbrace;
    else if (c == '}') out->sym = rbrace;
    else if (c == ',') out->sym = comma; 
    else if (c == '[') out->sym = lbrak; 
    else if (c == ']') out->sym = rbrak; 
    else if (c == ':') out->sym = colon;
    else if (c == '\"') {
        state = ST_STRING; reset(); //push(c);
    }
  }// if (state == ST_TERMINAL)...
  else if (state == ST_STRING){
    switch(c) {
      case '\\':
        if (escape()) pop(), push(c);
        else push(c);
        break;
      case '\"':
        if (escape()) pop(), push(c);
        else {
          state = ST_TERMINAL;
          out->sym = string;
          stack[tail] = 0;
          out->id  = stack;
          out->len = tail;
        }
        break;
      case '/':
        if (escape()) pop(), push(c);
        break;
      case 'r':
        if (escape()) pop(), push(0x0D);
        else push(c);
        break;
      case 'n':
        if (escape()) pop(), push(0x0A);
        else push(c);
        break;
      case 't':
        if (escape()) pop(), push(0x09);
        else push(c);
        break;
      case 'f':
        if (escape()) pop(), push(0x0C);
        else push(c);
        break;
      case 'b':
        if (escape()) pop(), push(0x08);
        else push(c);
        break;
      case 'u':
        if (escape()) pop(), state = ST_UNICODE;
        else push(c);
        break;
      default:
        push(c);
        break;
    }
  }// if (state == ST_STRING)...
  else if (state == ST_UNICODE) {
    //TODO: Add unicode support...
  }
  else if (state == ST_INTEGER) {
    if ((c >= '0' && c <= '9') || (c == '-')){
      push(c);
    }
    else if (c == '.') {
      state = ST_REAL; push(c);
    }
    else {
      state = ST_TERMINAL;
      accepted = false;
      out->sym = integer;
      stack[tail] = 0;
      out->id  = stack;
      out->len = tail;
    }
  }//if (state == ST_INTEGER)...
  else if (state == ST_REAL){
    if (c >= '0' && c <='9') {
      push(c);
    }
    else {
      state = ST_TERMINAL;
      accepted = false;
      out->sym = real;
      stack[tail] = 0;
      out->id  = stack;
      out->len = tail;
    }
  }//if (state == ST_REAL)...

  return accepted;
}